#include "matrices/matrix.h"

#include "matrix_test.h"
#include "cppunit/TestAssert.h"

#include <string>
#include <algorithm>

#define LAMBDA 1e-5



using namespace nymphaea;
using namespace nymphaea::matrices;

CPPUNIT_TEST_SUITE_REGISTRATION(MatrixTest);

MatrixTest::MatrixTest(): mpM1(NULL), mpM2(NULL)
{
	/* NULL */
}

void MatrixTest::setUp()
{
	mpM1 = new Matrix(2, 2);
	mpM2 = new Matrix(2, 2);
	mpM1->set_element_by_idx(0, 1, 1.0F);
	mpM2->set_element_by_idx(0, 1, 1.0F);
}

void MatrixTest::tearDown()
{
	delete mpM1;
	delete mpM2;
	mpM1 = NULL;
	mpM2 = NULL;
}

void MatrixTest::testSize()
{
	Matrix s2x2(2, 2);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("check row size", (size_t)2, s2x2.get_row_size());
	CPPUNIT_ASSERT_EQUAL_MESSAGE("check col size", (size_t)2, s2x2.get_col_size());

	Matrix s1x1(1, 1);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("check row size", (size_t)1, s1x1.get_row_size());
	CPPUNIT_ASSERT_EQUAL_MESSAGE("check col size", (size_t)1, s1x1.get_col_size());

	Matrix s4x5(4, 5);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("check row size", (size_t)4, s4x5.get_row_size());
	CPPUNIT_ASSERT_EQUAL_MESSAGE("check col size", (size_t)5, s4x5.get_col_size());
}

void MatrixTest::testEqual()
{
	Matrix a1x1(1, 1);
	Matrix b1x1(1, 1);
	CPPUNIT_ASSERT_MESSAGE("Matrix is equal.", a1x1==b1x1);
	a1x1.set_element_by_idx(0, 0, 3.14F);
	CPPUNIT_ASSERT_MESSAGE("Matrix is not equal.", !(a1x1==b1x1));
	b1x1.set_element_by_idx(0, 0, 3.14F);
	CPPUNIT_ASSERT_MESSAGE("Matrix is not equal.", a1x1==b1x1);


	Matrix a2x3(2, 3);
	Matrix b2x3(2, 3);
	CPPUNIT_ASSERT_MESSAGE("Matrix is equal.", a2x3==b2x3);
	a2x3.set_element_by_idx(0, 0, 3.14F);
	CPPUNIT_ASSERT_MESSAGE("Matrix is not equal.", !(a2x3==b2x3));
	b2x3.set_element_by_idx(0, 0, 3.14F);
	CPPUNIT_ASSERT_MESSAGE("Matrix is equal.", a2x3==b2x3);
	a2x3.set_element_by_idx(1, 2, 3.14F);
	CPPUNIT_ASSERT_MESSAGE("Matrix is not equal.", !(a2x3==b2x3));
	b2x3.set_element_by_idx(1, 2, 3.14F);
	CPPUNIT_ASSERT_MESSAGE("Matrix is equal.", a2x3==b2x3);

	Matrix a100x10000(100, 10000);
	Matrix b300x30000(300, 30000);
	CPPUNIT_ASSERT_MESSAGE("Matrix is not equal.", !(a100x10000==b300x30000));

}

void MatrixTest::testLoads()
{
	std::string data = load_bytes_from_file("data/unittest/matrices/example_matrix.json");

	Matrix a(1, 1);
	int ret = a.load_from_bytes(data);
	CPPUNIT_ASSERT_MESSAGE("load_from_bytes() failed.", ret==0);
	CPPUNIT_ASSERT_MESSAGE("size is match.", (3==a.get_row_size() && 2==a.get_col_size()));
	CPPUNIT_ASSERT_DOUBLES_EQUAL(a.get_element_by_idx(0, 0), 1.0F, LAMBDA);
	CPPUNIT_ASSERT_DOUBLES_EQUAL(a.get_element_by_idx(0, 1), 2.0F, LAMBDA);
	CPPUNIT_ASSERT_DOUBLES_EQUAL(a.get_element_by_idx(1, 0), 3.0F, LAMBDA);
	CPPUNIT_ASSERT_DOUBLES_EQUAL(a.get_element_by_idx(1, 1), 4.0F, LAMBDA);
	CPPUNIT_ASSERT_DOUBLES_EQUAL(a.get_element_by_idx(2, 0), 5.0F, LAMBDA);
	CPPUNIT_ASSERT_DOUBLES_EQUAL(a.get_element_by_idx(2, 1), 6.0F, LAMBDA);
	
	//2. test of single element matrix;
	//3. test of failed case;
}

void MatrixTest::testDumps()
{
	Matrix a(3, 2);
	a.set_element_by_idx(0, 0, 1.0F);
	a.set_element_by_idx(0, 1, 2.0F);
	a.set_element_by_idx(1, 0, 3.0F);
	a.set_element_by_idx(1, 1, 4.0F);
	a.set_element_by_idx(2, 0, 5.0F);
	a.set_element_by_idx(2, 1, 6.0F);

	std::string data(a.dump_to_bytes());

	Matrix b(1, 1);
	b.load_from_bytes(data);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", b.get_element_by_idx(0, 0), 1.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", b.get_element_by_idx(0, 1), 2.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", b.get_element_by_idx(1, 0), 3.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", b.get_element_by_idx(1, 1), 4.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", b.get_element_by_idx(2, 0), 5.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", b.get_element_by_idx(2, 1), 6.0);
}

void MatrixTest::testProduct()
{
	Matrix a(*mpM1);
	a.set_element_by_idx(1, 1, 3.14F);
	a.set_element_by_idx(0, 0, 2.0F);
	a.set_element_by_idx(1, 0, 90.0F);
	Matrix res = a*a;
	CPPUNIT_ASSERT_DOUBLES_EQUAL(res.get_element_by_idx(0, 0), 94.0F, LAMBDA);
	CPPUNIT_ASSERT_DOUBLES_EQUAL(res.get_element_by_idx(0, 1), 5.14F, LAMBDA);
	CPPUNIT_ASSERT_DOUBLES_EQUAL(res.get_element_by_idx(1, 0), 462.6F, LAMBDA);
	CPPUNIT_ASSERT_DOUBLES_EQUAL(res.get_element_by_idx(1, 1), 99.8596F, LAMBDA);
	Matrix id(a.get_col_size(), a.get_col_size());
	for(size_t i = 0; i<a.get_col_size(); ++i){
		id.set_element_by_idx(i, i, 1.0F);
	}
	CPPUNIT_ASSERT(a*id == a);

}

void MatrixTest::testInverse()
{
	Matrix a(2, 2);
	a.set_element_by_idx(0, 0, 2.0F);
	a.set_element_by_idx(0, 1, 1.0F);
	a.set_element_by_idx(1, 0, 90.0F);
	a.set_element_by_idx(1, 1, 3.14F);
	Matrix res = a.get_inverse();
	CPPUNIT_ASSERT_DOUBLES_EQUAL(res.get_element_by_idx(0, 0), -3.750597228858099e-002F, LAMBDA);
	CPPUNIT_ASSERT_DOUBLES_EQUAL(res.get_element_by_idx(0, 1), 1.194457716196847e-002F, LAMBDA);
	CPPUNIT_ASSERT_DOUBLES_EQUAL(res.get_element_by_idx(1, 0), 1.075011944577162e+000F, LAMBDA);
	CPPUNIT_ASSERT_DOUBLES_EQUAL(res.get_element_by_idx(1, 1), -2.388915432393694e-002F, LAMBDA);
}

void MatrixTest::testGetRow()
{
	Matrix a(3, 2);
	a.set_element_by_idx(0, 0, 1.0F);
	a.set_element_by_idx(0, 1, 2.0F);
	a.set_element_by_idx(1, 0, 3.0F);
	a.set_element_by_idx(1, 1, 4.0F);
	a.set_element_by_idx(2, 0, 5.0F);
	a.set_element_by_idx(2, 1, 6.0F);
	Matrix b(a.get_row_by_idx(0));
	CPPUNIT_ASSERT_MESSAGE("size is correct", b.get_row_size()==1 && b.get_col_size()==2);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("row is correct", b.get_element_by_idx(0, 0), 1.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("row is correct", b.get_element_by_idx(0, 1), 2.0);

	Matrix c(a.get_row_by_idx(2));
	CPPUNIT_ASSERT_MESSAGE("size is correct", c.get_row_size()==1 && c.get_col_size()==2);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("row is correct", c.get_element_by_idx(0, 0), 5.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("row is correct", c.get_element_by_idx(0, 1), 6.0);
}

void MatrixTest::testGetCol()
{
	Matrix a(3, 2);
	a.set_element_by_idx(0, 0, 1.0F);
	a.set_element_by_idx(0, 1, 2.0F);
	a.set_element_by_idx(1, 0, 3.0F);
	a.set_element_by_idx(1, 1, 4.0F);
	a.set_element_by_idx(2, 0, 5.0F);
	a.set_element_by_idx(2, 1, 6.0F);
	Matrix b(a.get_col_by_idx(0));
	CPPUNIT_ASSERT_MESSAGE("size is correct", b.get_col_size()==1 && b.get_row_size()==3);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("col is correct", b.get_element_by_idx(0, 0), 1.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("col is correct", b.get_element_by_idx(1, 0), 3.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("col is correct", b.get_element_by_idx(2, 0), 5.0);

	Matrix c(a.get_col_by_idx(1));
	CPPUNIT_ASSERT_MESSAGE("size is correct", c.get_col_size()==1 && c.get_row_size()==3);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("col is correct", c.get_element_by_idx(0, 0), 2.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("col is correct", c.get_element_by_idx(1, 0), 4.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("col is correct", c.get_element_by_idx(2, 0), 6.0);
}

void MatrixTest::testTranspose()
{
	Matrix a(3, 2);
	a.set_element_by_idx(0, 0, 1.0F);
	a.set_element_by_idx(0, 1, 2.0F);
	a.set_element_by_idx(1, 0, 3.0F);
	a.set_element_by_idx(1, 1, 4.0F);
	a.set_element_by_idx(2, 0, 5.0F);
	a.set_element_by_idx(2, 1, 6.0F);
	Matrix b(a.get_transposed());
	CPPUNIT_ASSERT_MESSAGE("size is correct", b.get_row_size()==2 && b.get_col_size()==3);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", b.get_element_by_idx(0, 0), 1.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", b.get_element_by_idx(0, 1), 3.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", b.get_element_by_idx(0, 2), 5.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", b.get_element_by_idx(1, 0), 2.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", b.get_element_by_idx(1, 1), 4.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", b.get_element_by_idx(1, 2), 6.0);
}

void MatrixTest::testDet()
{
	Matrix a(2, 2);
	a.set_element_by_idx(0, 0, 1.0F);
	a.set_element_by_idx(0, 1, 2.0F);
	a.set_element_by_idx(1, 0, 3.0F);
	a.set_element_by_idx(1, 1, 4.0F);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("det is correct", a.get_determinant(), (double)-2.0F);
}

void MatrixTest::testLUDecomp()
{
	Matrix a(2, 2);
	a.set_element_by_idx(0, 0, 1.0F);
	a.set_element_by_idx(0, 1, 2.0F);
	a.set_element_by_idx(1, 0, 3.0F);
	a.set_element_by_idx(1, 1, 4.0F);
	Matrix b(a.get_lu_decomp());
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", b.get_element_by_idx(0, 0), 1.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", b.get_element_by_idx(0, 1), 3.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", b.get_element_by_idx(1, 0), 5.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", b.get_element_by_idx(1, 1), 2.0);
}

void MatrixTest::testSum()
{
	Matrix a(3, 2);
	a.set_element_by_idx(0, 0, 1.0F);
	a.set_element_by_idx(0, 1, 2.0F);
	a.set_element_by_idx(1, 0, 3.0F);
	a.set_element_by_idx(1, 1, 4.0F);
	a.set_element_by_idx(2, 0, 5.0F);
	a.set_element_by_idx(2, 1, 6.0F);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("sum is correct", a.sum(), (double)21.0F);
}

void MatrixTest::testTransform()
{
	Matrix a(3, 2);
	a.set_element_by_idx(0, 0, 1.0F);
	a.set_element_by_idx(0, 1, 2.0F);
	a.set_element_by_idx(1, 0, 3.0F);
	a.set_element_by_idx(1, 1, 4.0F);
	a.set_element_by_idx(2, 0, 5.0F);
	a.set_element_by_idx(2, 1, 6.0F);
	a.transform(std::bind2nd(std::divides<double>(), 2.0));
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", a.get_element_by_idx(0, 0), 0.5);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", a.get_element_by_idx(0, 1), 1.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", a.get_element_by_idx(1, 0), 1.5);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", a.get_element_by_idx(1, 1), 2.0);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", a.get_element_by_idx(2, 0), 2.5);
	CPPUNIT_ASSERT_EQUAL_MESSAGE("elem is correct", a.get_element_by_idx(2, 1), 3.0);
}

void MatrixTest::testRowToVector()
{
	CPPUNIT_ASSERT_EQUAL_MESSAGE("not implement yet", true, false);
}

void MatrixTest::testColToVector()
{
	CPPUNIT_ASSERT_EQUAL_MESSAGE("not implement yet", true, false);
}

